package com.pocket.demo.core.security;

import com.pocket.demo.business.service.PocketUserDetailsService;
import com.pocket.demo.business.service.impl.UserDetailsServiceImpl;
import com.pocket.demo.core.security.filters.TokenAuthenticationTokenFilter;
import com.pocket.demo.core.security.filters.TokenContextFilter;
import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.session.RedisSessionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.*;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.*;
import java.util.stream.Stream;


/**
 * WebSecurityConfigurerAdapter 类 在Spring Security 5.7 后被弃用，并在Spring Security 6.0中正式移除。
 *      官方推荐使用 HttpSecurity 作更细粒度的配置方式。如：{@link SecurityConfig#securityFilterChain(HttpSecurity)}
 * @author zhaozhile
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //【demo-SpringSecurity-5.1】
//@EnableRedisRepositories TODO
public class SecurityConfig {

    @Resource
    private UserDetailsServiceImpl userDetailsService;

    @Resource
    private JwtDecoder jwkDecoder;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 设置登录端点【demo-SpringSecurity-3.2】、登录认证成功处理、登录认证失败处理【demo-SpringSecurity-3.9】
                .formLogin(login ->
                        login.loginProcessingUrl("/login")
                             .successHandler(authenticationHandler(userDetailsService))
                             .failureHandler(authenticationHandler(userDetailsService))
                            //.permitAll()
                )
                .logout(logout ->
                        // 设置退出端点【demo-SpringSecurity-3.10】
                        logout.logoutUrl("/logout")
                                .logoutSuccessHandler(authenticationHandler(userDetailsService))
                                //.addLogoutHandler()
                                //.invalidateHttpSession(true)
                                //.deleteCookies("JSESSIONID")
                )
                // 配置访问控制规则【demo-SpringSecurity-3.1】
                .authorizeHttpRequests(
                        requests -> {
                            requests.requestMatchers("/login", "/logout", "/oauth2/token","/instances", "/actuator/*","/actuator","/.well-known/oauth-authorization-server").anonymous();
                            requests.anyRequest().authenticated();
                            // 【demo-SpringSecurity-7.3】
//                            requests.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//                                 @Override
//                                 public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//                                     object.setAccessDecisionManager(myAccessDecisionManager);
//                                     object.setSecurityMetadataSource(myFilter);
//                                     return object;
//                                 }
//                            });
                        }
                )

                // 配置 OAuth2 资源服务器
                .oauth2ResourceServer(
                        resourceServer ->  {
                            // 检查 token 是否有效
                            resourceServer.jwt(Customizer.withDefaults());
                        }
                )

                //.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)

                // 配置认证管理器【demo-SpringSecurity-3.4】
                .authenticationManager(authenticationManager())

                .addFilterBefore(tokenContextFilter(), LogoutFilter.class)
                //【demo-SpringSecurity-4.4】
                .addFilterBefore(tokenAuthenticationTokenFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class)

                // Session 配置
                .sessionManagement(session -> {
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                    //session.maximumSessions(1).expiredSessionStrategy(authenticationHandler());
                })

                // 配置自定义认证上下文 【demo-SpringSecurity-3.6】
                .securityContext( context -> {
                    context.securityContextRepository(redisSecurityContextRepository(userDetailsService));
                })

                // 异常处理
                .exceptionHandling(exceptions -> exceptions
                        // 未认证时访问保护资源处理【demo-SpringSecurity-2】
                        .authenticationEntryPoint(jsonAuthenticationEntryPoint())
                        // 未授权时访问保护资源处理【demo-SpringSecurity-5.6】TODO 未生效
                        // 暂时在 GlobalExceptionHandler 中处理
                        .accessDeniedHandler(authenticationHandler(userDetailsService))
                )
                // TODO 开启
                .csrf(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        ;
        return http.build();
    }

    // 认证管理器：指定认证提供者、密码加密方式
    @Bean
    public AuthenticationManager authenticationManager() {
        // 用户/密码 数据库认证提供者
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        // JWT 认证提供者
        JwtAuthenticationProvider jwtProvider = new JwtAuthenticationProvider(jwkDecoder);
        return new ProviderManager(authProvider, jwtProvider);
    }

    @Bean
    public JsonAuthenticationEntryPoint jsonAuthenticationEntryPoint() {
        return new JsonAuthenticationEntryPoint();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * 【demo-SpringSecurity-4】
     *  默认使用的 DelegatingPasswordEncoder，要求存储的密码格式为: {id}password。框架会根据id判断密码加密方式。
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        //return new BCryptPasswordEncoder();

        // 使用如下方式，可定义多种密码加密方式，并指定默认加密方式。
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder(12));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }

    @Bean
    public AuthenticationHandler authenticationHandler(PocketUserDetailsService userDetailsService){
        return new AuthenticationHandler(userDetailsService);
    }

    @Bean
    public RedisSecurityContextRepository redisSecurityContextRepository(PocketUserDetailsService userDetailsService){
        return new RedisSecurityContextRepository(userDetailsService);
    }

    @Order(1)
    public TokenContextFilter tokenContextFilter(){
        return new TokenContextFilter();
    }

    @Order(2)
    public TokenAuthenticationTokenFilter tokenAuthenticationTokenFilter(PocketUserDetailsService userDetailsService){
        return new TokenAuthenticationTokenFilter(userDetailsService);
    }

    // @Bean TODO
    public RedisSessionProperties.ConfigureAction configureRedisAction() {
        return RedisSessionProperties.ConfigureAction.NONE;
    }

    //session策略，这里配置的是Header方式（有提供Header，Cookie等方式）
    // @Bean TODO
    public HttpSessionSecurityContextRepository httpSessionStrategy() {
        return new HttpSessionSecurityContextRepository();
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new  BCryptPasswordEncoder();
        Stream.of(1,2,3).forEach(
                item -> System.out.println(encoder.encode("pocket"))
        );
    }

}
